package Tests
{
	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Collision.Shapes.b2EdgeShape;
	import Box2D.Collision.Shapes.b2PolygonShape;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Common.Math.b2Vec2Array;
	import Box2D.Common.b2Color;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2FixtureDef;
	
	import Framework.Test;
	
	import org.osmf.layout.AbsoluteLayoutFacet;
	
	public class RayCast extends Test
	{
		private static const e_maxBodies:int = 256;
		private static const e_closest:int = 0;
		private static const e_any:int = 1;
		private static const e_multiple:int = 2;
		
		private var m_bodies:Vector.<b2Body> = new Vector.<b2Body>(e_maxBodies);
		private var m_userData:Vector.<int> = new Vector.<int>(e_maxBodies);
		private var m_polygons:Vector.<b2PolygonShape> = new Vector.<b2PolygonShape>(4);
		private var m_circle:b2CircleShape = new b2CircleShape;
		private var m_angle:Number;
		private var m_bodyIndex:int;
		private var m_mode:int;
		
		private var ground:b2Body;
		public function RayCast()
		{
			super();
			createGround();
			createShapes();
		}
		public override function get hotKeyNote():String
		{
			return 'Press 1-5 to drop stuff, m to change the mode';
		}
		private function createGround():void{
			var bd:b2BodyDef = new b2BodyDef;
			ground = m_world.CreateBody(bd);
			var shape:b2EdgeShape = new b2EdgeShape;
			shape.Set(new b2Vec2(-40,0),new b2Vec2(40,0));
			ground.CreateFixtureByShape(shape,0);
		}
		private function createShapes():void{
			for(var i:int=0;i<m_polygons.length;i++){
				m_polygons[i] = new b2PolygonShape;
			}
			
			var vertices:b2Vec2Array = new b2Vec2Array(8);
			
			vertices.setAt2(0,-.5,0);
			vertices.setAt2(1,.5,0);
			vertices.setAt2(2,0,1.5);
			m_polygons[0].Set(vertices,3);
			
			vertices.setAt2(0,-0.1, 0.0);
			vertices.setAt2(1,0.1, 0.0);
			vertices.setAt2(2,0.0, 1.5);
			m_polygons[1].Set(vertices, 3);
			
			var w:Number = 1;
			var b:Number = w/(2+Math.sqrt(2));
			var s:Number = Math.sqrt(2)*b;

			vertices.setAt2(0,0.5 * s, 0.0);
			vertices.setAt2(1,0.5 * w, b);
			vertices.setAt2(2,0.5 * w, b + s);
			vertices.setAt2(3,0.5 * s, w);
			vertices.setAt2(4,-0.5 * s, w);
			vertices.setAt2(5,-0.5 * w, b + s);
			vertices.setAt2(6,-0.5 * w, b);
			vertices.setAt2(7,-0.5 * s, 0.0);
			m_polygons[2].Set(vertices, 8);
			
			m_polygons[3].SetAsBox(.5,.5);
			
			m_circle.radius = .5;
			
			m_bodyIndex = 0;
			m_angle = 0;
			m_mode = e_closest;
		}
		private function Create(index:int):void
		{
			if (m_bodies[m_bodyIndex] != null)
			{
				m_world.DestroyBody(m_bodies[m_bodyIndex]);
				m_bodies[m_bodyIndex] = null;
			}
			
			var bd:b2BodyDef = new b2BodyDef;
			
			var x:Number = RandomFloat(-10.0, 10.0);
			var y:Number = RandomFloat(0.0, 20.0);
			bd.position.Set(x, y);
			bd.angle = RandomFloat(-Math.PI, Math.PI);
			
			m_userData[m_bodyIndex] = index;
			bd.userData = m_userData + m_bodyIndex;
			
			if (index == 4)
			{
				bd.angularDamping = 0.02;
			}
			
			m_bodies[m_bodyIndex] = m_world.CreateBody(bd);
			
			var fd:b2FixtureDef = new b2FixtureDef;
			if (index < 4)
			{
				fd.shape = m_polygons[index]
				fd.friction = 0.3;
				m_bodies[m_bodyIndex].CreateFixture(fd);
			}
			else
			{
				fd.shape = m_circle;
				fd.friction = 0.3;
				
				m_bodies[m_bodyIndex].CreateFixture(fd);
			}
			
			m_bodyIndex = (m_bodyIndex + 1) % e_maxBodies;
		}
		public function DestroyBody():void
		{
			for (var i:int = 0; i < e_maxBodies; ++i)
			{
				if (m_bodies[i] != null)
				{
					m_world.DestroyBody(m_bodies[i]);
					m_bodies[i] = null;
					return;
				}
			}
		}
		public override function Key(code:String):void
		{
			switch(code){
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
					Create(int(code)-1);
					break;
				case 'd':
					DestroyBody();
					break;
				case 'm':
					m_mode++;
					if(m_mode>e_multiple)
						m_mode=e_closest;
					break;
			}
		}
		public override function Step():void{
			super.Step();
			writeMessage('Mode = ' + m_mode)
			
			var L:Number = 11;
			var point1:b2Vec2 = new b2Vec2(0,10);
			var d:b2Vec2 = new b2Vec2(L*Math.cos(m_angle),L*Math.sin(m_angle));
			var point2:b2Vec2 = point1.Add(d);
			
			switch(m_mode){
				case e_closest:
					testClosestCallback(point1,point2);
					break;
				case e_any:
					testAnyCallback(point1,point2);
					break;
				case e_multiple:
					testMultipleCallback(point1,point2);
					break;
			}
			m_angle += 0.25 * Math.PI / 180.0;
		}
		private function testClosestCallback(point1:b2Vec2,point2:b2Vec2):void{
			var callback:RayCastClosestCallback = new RayCastClosestCallback;
			m_world.RayCast(callback,point1,point2);
			var color:b2Color = new b2Color;
			if (callback.m_hit)
			{
				color.Set(0.4, 0.9, 0.4)
				m_debugDraw.DrawPoint(callback.m_point, 2.0,color);
				color.Set(0.8, 0.8, 0.8)
				m_debugDraw.DrawSegment(point1, callback.m_point,color);
				var head:b2Vec2 = callback.m_point.Add(callback.m_normal.Multiply(.5));
				color.Set(0.9, 0.9, 0.4)
				m_debugDraw.DrawSegment(callback.m_point, head,color);
			}
			else
			{
				color.Set(0.8, 0.8, 0.8)
				m_debugDraw.DrawSegment(point1, point2, color);
			}
		} 
		private function testAnyCallback(point1:b2Vec2,point2:b2Vec2):void{
			var callback:RayCastAnyCallback = new RayCastAnyCallback;
			m_world.RayCast(callback, point1, point2);
			
			var color:b2Color = new b2Color;
			if (callback.m_hit)
			{
				color.Set(0.4, 0.9, 0.4)
				m_debugDraw.DrawPoint(callback.m_point, 2, color);
				color.Set(0.8, 0.8, 0.8)
				m_debugDraw.DrawSegment(point1, callback.m_point,color);
				var head:b2Vec2 = callback.m_point.Add(callback.m_normal.Multiply(.5));
				color.Set(0.9, 0.9, 0.4)
				m_debugDraw.DrawSegment(callback.m_point, head,color);
			}
			else
			{
				color.Set(0.8, 0.8, 0.8)
				m_debugDraw.DrawSegment(point1, point2, color);
			}			
		}
		private function testMultipleCallback(point1:b2Vec2,point2:b2Vec2):void{
			var callback:RayCastMultipleCallback = new RayCastMultipleCallback;
			m_world.RayCast(callback, point1, point2);

			var color:b2Color = new b2Color;
			color.Set(.8,.8,.8)
			m_debugDraw.DrawSegment(point1, point2,color);
			
			for (var i:int = 0; i < callback.m_count; ++i)
			{
				var p:b2Vec2 = callback.m_points[i];
				var n:b2Vec2 = callback.m_normals[i];
				color.Set(0.4, 0.9, 0.4);
				m_debugDraw.DrawPoint(p, 2.0, color);
				color.Set(0.8, 0.8, 0.8)
				m_debugDraw.DrawSegment(point1, p,color);
				var head:b2Vec2 = p.Add(n.Multiply(.5));
				color.Set(0.9, 0.9, 0.4)
				m_debugDraw.DrawSegment(p, head, color);
			}			
		}
	}
}
import Box2D.Common.Math.b2Vec2;
import Box2D.Common.b2Base;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2Fixture;
import Box2D.Dynamics.b2RayCastCallback;

import flashx.textLayout.debug.assert;

class RayCastClosestCallback extends b2RayCastCallback
{
	public var m_hit:Boolean;
	public var m_point:b2Vec2;
	public var m_normal:b2Vec2;
	public override function ReportFixture(fixture:b2Fixture, point:b2Vec2, normal:b2Vec2, fraction:Number):Number
	{
		var body:b2Body = fixture.GetBody();
		var userData:Object = body.GetUserData();
		if(userData!=null){
			var index:int = userData as int;
			if(index==0)
			{
				return -1;
			}
		}
		m_hit = true;
		m_point = point;
		m_normal = normal;
		return fraction
	}
}
// This callback finds any hit. Polygon 0 is filtered.
class RayCastAnyCallback extends b2RayCastCallback
{
	public var m_hit:Boolean;
	public var m_point:b2Vec2;
	public var m_normal:b2Vec2;	
	public override function ReportFixture(	fixture:b2Fixture, point:b2Vec2,
		normal:b2Vec2, fraction:Number):Number
	{
		var body:b2Body = fixture.GetBody();
		var userData:Object = body.GetUserData();
		if (userData!=null)
		{
			var index:int = userData as int;
			if(index==0)
			{
				return -1;
			}
		}
		
		m_hit = true;
		m_point = point;
		m_normal = normal;
		return 0.0;
	}
}
// This ray cast collects multiple hits along the ray. Polygon 0 is filtered.
class RayCastMultipleCallback extends b2RayCastCallback
{
	private static const e_maxCount:int = 3;
	public var m_points:Vector.<b2Vec2> = new Vector.<b2Vec2>(e_maxCount);
	public var m_normals:Vector.<b2Vec2> = new Vector.<b2Vec2>(e_maxCount);
	public var m_count:int;
	public override function ReportFixture(	fixture:b2Fixture, point:b2Vec2,
											normal:b2Vec2, fraction:Number):Number
	{
		var body:b2Body = fixture.GetBody();
		var userData:Object = body.GetUserData();
		if (userData!=null)
		{
			var index:int = userData as int;
			if(index==0)
			{
				return -1;
			}
		}	
		m_points[m_count] = point;
		m_normals[m_count] = normal;
		++m_count;
		
		if (m_count == e_maxCount)
		{
			return 0.0;
		}
		
		return 1.0;		
	}
}